﻿using Lung_Sounds_V2;
using LungSoundWizard.Classes;
using pHMb.Spectrogram;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace LungSoundWizard
{
    public class SoundSelectionHandler
    {
        public SpectrogramPane Spectrogram { get; set; }
        public double FftWindowSize { get; set; }

        public string WavFileName { get; set; }

        public SoundSelectionHandler(SpectrogramPane spectrogram, double windowSize)
        {
            Spectrogram = spectrogram;
            FftWindowSize = windowSize;
        }

        public enum AverageFftTypes
        {
            Comp, SNR, Normal
        };
        public static Dictionary<string, double[]> GenerateAverageFFT(List<SpectrogramPane.XSelection> selections, 
            AverageFftTypes fftType = AverageFftTypes.Comp)
        {
            Dictionary<string, double[]> avgFFTs = new Dictionary<string, double[]>(selections.Count);
            Dictionary<string, double[]> avgFFTsDenoise = new Dictionary<string, double[]>(selections.Count);
            Dictionary<string, double[]> snrs = new Dictionary<string, double[]>(selections.Count);
            Dictionary<string, double[]> compSnrs = new Dictionary<string, double[]>(selections.Count);

            // Performs analysis to find average frequency data
            if (selections.Count > 0)
            {
                double windowSize = selections[0].ParentPane.WindowSize;

                // Get highest average amp.
                double maxAvgAmp = 0;
                for (int i = 1; i < selections.Count; i++)
                {
                    SpectrogramPane.XSelection selection = selections[i];

                    double currentAvgAmp = CalcAvgAmplitude(selection.AverageFFT, 100, 2000, windowSize, false, true);

                    if (currentAvgAmp > maxAvgAmp)
                    {
                        maxAvgAmp = currentAvgAmp;
                    }
                }

                for (int i = 0; i < selections.Count; i++)
                {
                    SpectrogramPane.XSelection selection = selections[i];

                    if (selection.NoiseSelection == null)
                    {
                        selection.NoiseSelection = selections[0];
                    }

                    //snrs.Add(selection.ToString(), selection.SNR);
                    snrs.Add(selection.ToString(), selection.NormSNR);

                    double[] compSnr = new double[selection.SNR.Length];

                    uint numberBinsIn1Stdev = 0;
                    uint numberBinsIn2Stdev = 0;

                    int stDevMin = (int)(100 / (1.00 / selection.ParentPane.WindowSize));
                    int stDevMax = (int)(2000 / (1.00 / selection.ParentPane.WindowSize));

                    for (int j = 0; j < compSnr.Length; j++)
                    {
                        compSnr[j] = (selection.NormSNR[j] - ReferenceSnippetReader.Snippets.Average[j]) / ReferenceSnippetReader.Snippets.StDev[j];

                        if (j <= stDevMax && j >= stDevMin)
                        {
                            if (compSnr[j] < 1 && compSnr[j] > -1)
                            {
                                numberBinsIn1Stdev++;
                            }

                            if (compSnr[j] < 2 && compSnr[j] > -2)
                            {
                                numberBinsIn2Stdev++;
                            }
                        }
                    }
                    compSnrs.Add(string.Format("{0} S1:{1}%,S2:{2}%", selection.Name,
                        (numberBinsIn1Stdev * 100) / (stDevMax - stDevMin),
                        (numberBinsIn2Stdev * 100) / (stDevMax - stDevMin)), compSnr);

                    avgFFTs.Add(selection.ToString(), selection.AverageFFT);

                    //avgFFTsDenoise.Add(selection.ToString(), RemoveNoise(avgFFT, noiseFFT, maxAvgAmp));
                }

                //compSnrs.Add("StDev+", ReferenceSnippetReader.Snippets.StDev);
                //compSnrs.Add("StDev-", ReferenceSnippetReader.Snippets.StDevNeg);

                compSnrs.Add("StDev+", CreateLine(ReferenceSnippetReader.Snippets.Average.Length, 1));
                compSnrs.Add("StDev-", CreateLine(ReferenceSnippetReader.Snippets.Average.Length, -1));
                compSnrs.Add("StDev2+", CreateLine(ReferenceSnippetReader.Snippets.Average.Length, 2));
                compSnrs.Add("StDev2-", CreateLine(ReferenceSnippetReader.Snippets.Average.Length, -2));

                switch (fftType)
                {
                    case AverageFftTypes.Comp:
                        return compSnrs;

                    case AverageFftTypes.Normal:
                        return avgFFTs;

                    case AverageFftTypes.SNR:
                        return snrs;

                    default:
                        return compSnrs;
                }
            }
            else
            {
                return null;
            }
        }

        private static double[] CreateLine(int count, double amp)
        {
            double[] line = new double[count];

            for (int i = 0; i < count; i++)
            {
                line[i] = amp;
            }

            return line;
        }

        private static double[] ArrayMult(double[] array, double mult)
        {
            double[] newArray = new double[array.Length];

            for (int i = 0; i < array.Length; i++)
            {
                newArray[i] = array[i] * mult;
            }

            return newArray;
        }

        private static double[] Normalise(double[] snr, double windowSize)
        {
            double avgDb100_2000 = CalcAvgAmplitude(snr, 100, 2000, windowSize, false, false);
            //double avgDb100_2000_Avg = CalcAvgAmplitude(_avgLung, 100, 2000, false, false);

            double normFactor = avgDb100_2000 - 0;

            double[] normSnr = new double[snr.Length];
            for (int i = 0; i < snr.Length; i++)
            {
                normSnr[i] = snr[i] - normFactor;
            }

            return normSnr;
        }

        private static double CalcAvgAmplitude(double[] avgFft, int startF, int endF, double windowSize, bool absIn = false, bool absOut = false)
        {
            double avgAmp = 0;
            int x = 0;
            for (x = (int)(startF / (1.00 / windowSize)); x < (int)(endF / (1.00 / windowSize)); x++)
            //for (x = 0; x < avgFft.Length; x++)
            {
                if (absIn)
                {
                    avgAmp += avgFft[x];
                }
                else
                {
                    avgAmp += Math.Pow(10, avgFft[x] / 20.00);
                }
            }

            avgAmp /= x;

            if (absOut)
            {
                return avgAmp;
            }
            else
            {
                return 20 * Math.Log10(avgAmp);
            }
        }

        public void SaveSelections()
        {
            using (BinaryWriter selectionsWriter = new BinaryWriter(File.Open(WavFileName.ToLower().Replace(".wav", ".spsel"), FileMode.Create)))
            {
                selectionsWriter.Write(new char[] { 'P', 'H', 'S', 'P', 
                                                    'E', 'C', 'T', '\0', 
                                                    'V', '1', '0', '2' });

                selectionsWriter.Write(Spectrogram.SelectedXPoints.Count);

                WriteSelections(Spectrogram.SelectedXPoints, selectionsWriter);
            }
        }

        public static void WriteSelections(ObservableCollection<SpectrogramPane.XSelection> selections, BinaryWriter selectionsWriter)
        {
            SpectrogramPane.XSelection currentSelection;
            for (int i = 0; i < selections.Count; i++)
            {
                currentSelection = selections[i];

                WriteSelection(currentSelection, selectionsWriter, selections);
            }
        }

        public static void WriteSelection(SpectrogramPane.XSelection selection, BinaryWriter selectionsWriter, ObservableCollection<SpectrogramPane.XSelection> selections)
        {
            selectionsWriter.Write(selection.Name);

            selectionsWriter.Write(selection.StartX);
            selectionsWriter.Write(selection.EndX);
            selectionsWriter.Write(selection.StartLineColor.ToArgb());
            selectionsWriter.Write(selection.EndLineColor.ToArgb());

            if (selection.NoiseSelection != null && selection.NoiseSelection != selection)
            {
                selectionsWriter.Write(selections.IndexOf(selection.NoiseSelection));
            }
            else
            {
                selectionsWriter.Write(-1);
            }

            if (selection.GetType() == typeof(SpectrogramPane.XSelectionGroup))
            {
                SpectrogramPane.XSelectionGroup currentSelGrp = (SpectrogramPane.XSelectionGroup)selection;

                // Number of children
                selectionsWriter.Write(currentSelGrp.ChildSelections.Count);
                foreach (SpectrogramPane.XSelection childSel in currentSelGrp.ChildSelections)
                {
                    WriteSelection(childSel, selectionsWriter, selections);
                }
            }
            else
            {
                selectionsWriter.Write(0); // No children
            }
        }

        public static SpectrogramPane.XSelection LoadSelection(BinaryReader selectionsReader, SpectrogramPane spect, bool includesNoise = true, 
            Dictionary<SpectrogramPane.XSelection, int> noiseSelections = null, int fileOffset = 0, int noiseIndexOffset = 0)
        {
            string name = selectionsReader.ReadString();
            int startX = selectionsReader.ReadInt32() + fileOffset;
            int endX = selectionsReader.ReadInt32() + fileOffset;

            byte[] startColor = selectionsReader.ReadBytes(4);
            byte[] endColor = selectionsReader.ReadBytes(4);

            int noiseIndex = -1;
            if (includesNoise && noiseSelections != null)
            {
                noiseIndex = selectionsReader.ReadInt32() + noiseIndexOffset;
            }

            int childCount = selectionsReader.ReadInt32();

            SpectrogramPane.XSelection selection;

            if (childCount > 0)
            {
                selection = new SpectrogramPane.XSelectionGroup(spect);

                for (int i = 0; i < childCount; i++)
                {
                    ((SpectrogramPane.XSelectionGroup)selection).ChildSelections.Add(LoadSelection(selectionsReader, spect, 
                        includesNoise, noiseSelections, fileOffset, noiseIndexOffset));
                }
            }
            else
            {
                selection = new SpectrogramPane.XSelection(spect);
            }

            selection.Name = name;
            selection.StartX = startX;
            selection.EndX = endX;

            selection.StartLineColor = System.Drawing.Color.FromArgb(startColor[3], startColor[2], startColor[1], startColor[0]);
            selection.EndLineColor = System.Drawing.Color.FromArgb(endColor[3], endColor[2], endColor[1], endColor[0]);

            if (noiseSelections != null)
            {
                noiseSelections.Add(selection, noiseIndex);
            }

            return selection;
        }

        public void LoadSelections(int fileOffset = 0)
        {
            if (File.Exists(WavFileName.ToLower().Replace(".wav", ".spsel")))
            {
                using (BinaryReader selectionsReader = new BinaryReader(File.Open(WavFileName.ToLower().Replace(".wav", ".spsel"), FileMode.Open)))
                {
                    if (new string(selectionsReader.ReadChars(7)) == "PHSPECT")
                    {
                        selectionsReader.ReadChar();
                        string verString = new string(selectionsReader.ReadChars(4));

                        int numSelections;
                        switch (verString)
                        {
                            case "V100":
                                numSelections = selectionsReader.ReadInt32();

                                for (int i = 0; i < numSelections; i++)
                                {
                                    SpectrogramPane.XSelection selection = new SpectrogramPane.XSelection(Spectrogram);

                                    selection.Name = selectionsReader.ReadString();
                                    selection.StartX = selectionsReader.ReadInt32() + fileOffset;
                                    selection.EndX = selectionsReader.ReadInt32() + fileOffset;

                                    byte[] startColor = selectionsReader.ReadBytes(4);
                                    byte[] endColor = selectionsReader.ReadBytes(4);

                                    selection.StartLineColor = System.Drawing.Color.FromArgb(startColor[3], startColor[2], startColor[1], startColor[0]);
                                    selection.EndLineColor = System.Drawing.Color.FromArgb(endColor[3], endColor[2], endColor[1], endColor[0]);

                                    Spectrogram.SelectedXPoints.Add(selection);
                                }
                                break;

                            case "V101":
                                numSelections = selectionsReader.ReadInt32();

                                for (int i = 0; i < numSelections; i++)
                                {
                                    Spectrogram.SelectedXPoints.Add(LoadSelection(selectionsReader, Spectrogram, false));
                                }
                                break;

                            case "V102":
                                numSelections = selectionsReader.ReadInt32();

                                Dictionary<SpectrogramPane.XSelection, int> noiseSelections = new Dictionary<SpectrogramPane.XSelection,int>();
                                for (int i = 0; i < numSelections; i++)
                                {
                                    Spectrogram.SelectedXPoints.Add(LoadSelection(selectionsReader, Spectrogram, true, noiseSelections, fileOffset));
                                }

                                loadNoiseSels(Spectrogram.SelectedXPoints, noiseSelections, Spectrogram.SelectedXPoints);
                                break;

                            default:
                                MessageBox.Show("Selections file not compatible with this version.\nSelections not loaded.", "Incompatible selections", MessageBoxButton.OK, MessageBoxImage.Error);
                                break;
                        }

                        
                    }
                    else
                    {
                        Console.WriteLine("Invalid selections file");
                    }
                }
            }
        }

        public static void loadNoiseSels(IEnumerable<SpectrogramPane.XSelection> selections, 
            Dictionary<SpectrogramPane.XSelection, int> noiseSelections, ObservableCollection<SpectrogramPane.XSelection> selectionsTarget)
        {
            foreach (SpectrogramPane.XSelection selection in selections)
            {
                if (selection.GetType() == typeof(SpectrogramPane.XSelectionGroup))
                {
                    loadNoiseSels(((SpectrogramPane.XSelectionGroup)selection).ChildSelections, noiseSelections, selectionsTarget);
                }

                if (noiseSelections.ContainsKey(selection) && noiseSelections[selection] >= 0)
                {
                    selection.NoiseSelection = selectionsTarget[noiseSelections[selection]];
                }
            }
        }

        public void AutoName()
        {
            RenameWindow renameDialog = new RenameWindow(true);

            if (renameDialog.ShowDialog() == true)
            {
                for (int i = 0; i < Spectrogram.SelectedXPoints.Count; i++)
                {
                    SpectrogramPane.XSelection selection = Spectrogram.SelectedXPoints[i];

                    if (i == 0)
                    {
                        selection.Name = renameDialog.Name + ":Noise";
                    }
                    else if ((i - 1) % 2 == 0)
                    {
                        if (renameDialog.StartsWithIn)
                        {
                            selection.Name = string.Format("{0}:In.{1}", renameDialog.Name, (i - 1) / 2 + 1);
                        }
                        else
                        {
                            selection.Name = string.Format("{0}:Out.{1}", renameDialog.Name, (i - 1) / 2 + 1);
                        }
                    }
                    else
                    {
                        if (renameDialog.StartsWithIn)
                        {
                            selection.Name = string.Format("{0}:Out.{1}", renameDialog.Name, (i - 1) / 2 + 1);
                        }
                        else
                        {
                            selection.Name = string.Format("{0}:In.{1}", renameDialog.Name, (i - 1) / 2 + 1);
                        }
                    }
                }
            }
        }

        public void HighlightSelections(List<SpectrogramPane.XSelection> selectedSelections)
        {
            foreach (SpectrogramPane.XSelection selection in Spectrogram.SelectedXPoints)
            {
                selection.IsHighlighted = selectedSelections.Contains(selection);
            }

            Spectrogram.UpdateOverlay();
        }

        public void GroupSelections(List<SpectrogramPane.XSelection> selectedSelections)
        {
            if (selectedSelections.Count > 1)
            {
                RenameWindow renameWin = new RenameWindow();

                renameWin.Name = "New Group";

                if (renameWin.ShowDialog() == true)
                {
                    SpectrogramPane.XSelectionGroup newSelGroup = new SpectrogramPane.XSelectionGroup(Spectrogram);

                    foreach (SpectrogramPane.XSelection sel in selectedSelections)
                    {
                        newSelGroup.ChildSelections.Add(sel);
                    }

                    newSelGroup.Name = renameWin.Name;

                    Spectrogram.SelectedXPoints.Add(newSelGroup);
                }
            }
        }
       
        public void RenameSelection(SpectrogramPane.XSelection selection)
        {
            RenameWindow renameWin = new RenameWindow();

            renameWin.Name = selection.Name;

            if (renameWin.ShowDialog() == true)
            {
                selection.Name = renameWin.Name;
            }
        }

        public void SetNoise(List<SpectrogramPane.XSelection> selectedSelections)
        {
            foreach (SpectrogramPane.XSelection currentSel in selectedSelections)
            {
                if (currentSel.GetType() == typeof(SpectrogramPane.XSelectionGroup))
                {
                    MessageBox.Show("You cannot set the noise selection for a group", "Noise Selection", 
                        MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }
            }

            SetNoiseWindow setWindow = new SetNoiseWindow(Spectrogram);

            setWindow.Selection = ((SpectrogramPane.XSelection)selectedSelections[0]).NoiseSelection;

            if (setWindow.ShowDialog() == true)
            {
                foreach (SpectrogramPane.XSelection currentSel in selectedSelections)
                {
                    currentSel.NoiseSelection = setWindow.Selection;
                }
            }
        }
    }
}
